﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using LingDong.IndexEntities.Document;
using LingDong.IndexEntities.Helper;
using LingDong.IndexMerger.Helper;
using LingDong.SerializeProvider;
using NLog;

namespace LingDong.IndexMerger
{
    internal class DocumentIndex : IDisposable
    {
        #region Constructor

        internal DocumentIndex()
        {
            documentOffset = new Dictionary<int, DocumentOffset>();

            parsedDocStream = new FileStream(IndexNameHelper.GetParsedDocumentFileName(), FileMode.Create);
            oriDocStream = new FileStream(IndexNameHelper.GetOriginalDocumentFileName(), FileMode.Create);
            dicOldDocIDToNewDocID = new Dictionary<KeyValuePair<int, int>, int>();
        }

        #endregion

        
        #region Internal Methods
        
        /// <summary>
        /// Merge all document related subindex to merged index
        /// Include X.DocumentIndex, X.OriginalDocument, X.ParsedDocument
        /// "X" increases from 0 to max index in SubIndex directory
        /// </summary>
        internal void Merge()
        {
            // subindex file names
            List<string> docIndexFnames;
            List<string> parsedDocFnames;
            List<string> oriDocFnames;

            // original documents and parsed documents
            int indexCount = SubIndexNameHelper.GetDocumentFileNameList(out docIndexFnames, out parsedDocFnames, out oriDocFnames);
            for (int i = 0; i < indexCount; i++)
            {
                CopyOneSubIndex(docIndexFnames[i], parsedDocFnames[i], oriDocFnames[i], i);
            }

            try
            {
                // save new offset to new index
                using (FileStream fs = new FileStream(IndexNameHelper.GetDocumentIndexFileName(), FileMode.Create))
                {
                    Serializer.Serialize<Dictionary<int, DocumentOffset>>(fs, documentOffset);
                }
            }
            catch (Exception ex)
            {
                Logger logger = LogManager.GetCurrentClassLogger();
                logger.Error("In index mege, document index, save new offset to new index: {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Get new doc id from old sub index prefix and old doc id
        /// </summary>
        /// <param name="subIndexPrefix"></param>
        /// <param name="oldDocID"></param>
        /// <returns></returns>
        internal int GetNewDocIDFromOldDocID(int subIndexPrefix, int oldDocID)
        {
            return dicOldDocIDToNewDocID[new KeyValuePair<int, int>(subIndexPrefix, oldDocID)];
        }

        #endregion


        #region Private Methods

        /// <summary>
        /// Copy subindex files with specified prefix to merged index
        /// </summary>
        /// <param name="docIndex">document index file name</param>
        /// <param name="parsedDoc">parsed document file name</param>
        /// <param name="oriDoc">original document file name</param>
        /// <param name="prefix">the "X" in X.****</param>
        private void CopyOneSubIndex(string docIndex, string parsedDoc, string oriDoc, int prefix)
        {
            try
            {
                // deserialize document offset
                Dictionary<int, DocumentOffset> subIndexDocumentOffset;
                using (FileStream fs = new FileStream(docIndex, FileMode.Open))
                {
                    subIndexDocumentOffset = Serializer.Deserialize<Dictionary<int, DocumentOffset>>(fs);
                }

                using (FileStream subIndexParsedDocStream = new FileStream(parsedDoc, FileMode.Open))
                using (FileStream subIndexOriDocStream = new FileStream(oriDoc, FileMode.Open))
                {
                    foreach (DocumentOffset offset in subIndexDocumentOffset.Values)
                    {
                        CopyOneDocument(subIndexParsedDocStream, subIndexOriDocStream, offset, prefix);
                    }
                }
            }
            catch (Exception ex)
            {
                Logger logger = LogManager.GetCurrentClassLogger();
                logger.Error("In index mege, document index, CopyOneSubIndex: {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Copy one document from sub index to merged index
        /// Including parsed documents and original documents
        /// </summary>
        /// <param name="subIndexParsedDocStream">parsed document stream in sub idnex</param>
        /// <param name="subIndexOriDocStream">original document stream in sub idnex</param>
        /// <param name="offset">stream offset</param>
        /// <param name="prefix">the "X" in X.****</param>
        private void CopyOneDocument(FileStream subIndexParsedDocStream, FileStream subIndexOriDocStream, DocumentOffset offset, int prefix)
        {
            // save new document offset
            DocumentOffset newOffset = new DocumentOffset(parsedDocStream.Position, oriDocStream.Position);
            documentOffset[currentDocumentID] = newOffset;

            // copy parsed document to new index
            subIndexParsedDocStream.Position = offset.ParsedDocumentOffset;
            ParsedDocumentEntity parsed = Serializer.Deserialize<ParsedDocumentEntity>(subIndexParsedDocStream);
            int parsedDocID = parsed.DocID;
            parsed.DocID = currentDocumentID;
            Serializer.Serialize<ParsedDocumentEntity>(parsedDocStream, parsed);

            // copy original document to new index
            subIndexOriDocStream.Position = offset.OriginalDocumentOffset;
            OriginalDocumentEnity ori = Serializer.Deserialize<OriginalDocumentEnity>(subIndexOriDocStream);
            int oriDocID = ori.DocID;
            ori.DocID = currentDocumentID;
            Serializer.Serialize<OriginalDocumentEnity>(oriDocStream, ori);

            // save the mapping from old doc id to new doc id
            Debug.Assert(parsedDocID == oriDocID);
            dicOldDocIDToNewDocID.Add(new KeyValuePair<int, int>(prefix, oriDocID), currentDocumentID);
            currentDocumentID++;
        }

        #endregion
        

        #region Private Members

        private int currentDocumentID;
        private Dictionary<int, DocumentOffset> documentOffset;
        private FileStream parsedDocStream;
        private FileStream oriDocStream;

        /// <summary>
        /// [(old sub index, old id), new id]
        /// </summary>
        private Dictionary<KeyValuePair<int, int>, int> dicOldDocIDToNewDocID;

        #endregion
        

        #region Disposable related

        ~DocumentIndex()
        {
            Dispose();
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            // protect from being called multiple times.
            if (disposed)
            {
                return;
            }

            if (disposing)
            {
                // clean up all managed resources.
                if (parsedDocStream != null)
                {
                    parsedDocStream.Dispose();
                }
                if (oriDocStream != null)
                {
                    oriDocStream.Dispose();
                }
            }

            disposed = true;
        }

        private bool disposed = false;

        #endregion
    }
}
